package com.clear.honghefinance.common.service.impl;

import com.clear.honghefinance.common.config.SecurityConfig;
import com.clear.honghefinance.common.constant.CommonConstant;
import com.clear.honghefinance.common.dto.BaseUserInfoDTO;
import com.clear.honghefinance.common.exception.BizException;
import com.clear.honghefinance.common.exception.LoginException;
import com.clear.honghefinance.common.service.AuthFilterService;
import com.clear.honghefinance.common.service.TokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@ConditionalOnProperty(prefix = "sys", name = "enable-my-security", havingValue = "true")   // bean的注入条件
@Component
@Slf4j
@RequiredArgsConstructor
public class AuthFilterServiceImpl<T extends BaseUserInfoDTO> implements AuthFilterService<T> {

    private final TokenService<T> tokenService;
    private final SecurityConfig securityConfig;
    private final AntPathMatcher antPathMatcher;
    private final RedisTemplate<String, Object> redisTemplate;
    private final HandlerExceptionResolver handlerExceptionResolver;

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("开始鉴权，路径：{},", request.getRequestURI());
        try {
            if (Objects.isNull(securityConfig) || !securityConfig.getEnable()) {
                filterChain.doFilter(request, response);
                return;
            }

            T userInfo = null;
            if ("token".equals(securityConfig.getGetUserType())) {
                // 从token中获取用户信息
                // api-access-token 由前后端协商的属性
                String token = request.getHeader("api-access-token");
                userInfo = tokenService.checkToken(token);
            } else if ("gateway".equals(securityConfig.getGetUserType())) {
                // todo 从网关中获取用户信息

            }
            // 未获取到用户信息，抛出异常
            if (Objects.isNull(userInfo)) {
                throw new LoginException("无法获取到用户信息");
            }
            BaseUserInfoDTO baseUserInfoDTO = (BaseUserInfoDTO) userInfo;
            // 鉴权
            checkPermissions(baseUserInfoDTO.getSysRoleIds(), request.getServletPath());
            // 存储用户信息在threadLocal中
            tokenService.setThreadLocalUser(userInfo);
            // 放行
            filterChain.doFilter(request, response);
            // 清除用户信息
            tokenService.removeThreadLocalUser();
        } catch (Exception e) {
            handlerExceptionResolver.resolveException(request, response, null, e);
        }
    }

    /**
     * 不经过过滤器筛选
     *
     * @param request
     * @return
     * @throws ServletException
     */
    @Override
    public boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        if (Objects.isNull(securityConfig) || !securityConfig.getEnable() || CollectionUtils.isEmpty(securityConfig.getIgnores())) {
            return false;
        }
        String path = request.getServletPath();
        boolean ignore = securityConfig.getIgnores().stream().anyMatch(p -> antPathMatcher.match(p, path));
        if (log.isInfoEnabled()) {
            log.info("路径：{}，是否忽略权限过滤：{}", path, ignore);
        }
        return ignore;
    }

    /**
     * 检查用户权限
     *
     * @param sysRoleIds 用户角色id列表
     * @param path       当前请求路径
     */
    private void checkPermissions(Set<Long> sysRoleIds, String path) {
        // 管理员角色不检查权限
        if (sysRoleIds.contains(CommonConstant.ROLE_ADMIN)) {
            return;
        }
        Set<String> roleBindResourcePaths = listRoleResourcePathByCache(sysRoleIds);
        if (org.springframework.util.CollectionUtils.isEmpty(roleBindResourcePaths)) {
            throw new BizException("角色没有绑定任何资源");
        }
        // 遍历资源列表，检查当前请求路径是否匹配任一资源路径
        for (String url : roleBindResourcePaths) {
            if (antPathMatcher.match(url, path)) {
                // 如果路径匹配，则返回，表示权限检查通过
                return;
            }
        }
        throw new BizException("权限不足");
    }

    /**
     * 从缓存中获取角色对应的资源id列表
     *
     * @param roleIds
     * @return
     */
    private Set<String> listRoleResourcePathByCache(Set<Long> roleIds) {
        HashOperations<String, String, Set<String>> hashOps = redisTemplate.opsForHash();
        List<Set<String>> roleMenuIds = hashOps.multiGet(CommonConstant.ROLE_RESOURCE_PERMISSIONS,
                roleIds.stream().map(String::valueOf).collect(Collectors.toSet()));
        // 对结果进行处理，将 List<List<String>> 转为 List<String>
        return roleMenuIds.stream()
                .filter(p -> !CollectionUtils.isEmpty(p))
                .flatMap(Set::stream)
                .collect(Collectors.toSet());
    }
}
